using System;

namespace GoodroommateComputingPlatform.Algorithms.BackPropagationDeep
{
        public class BackPropagationDeep {

            public double[][] layer;// 神经网络各层节点
            private double[][] layerErr;// 神经网络各节点误差
            public double[][,] layer_weight;// 各层节点权重
            private double[][,] layer_weight_delta;// 各层节点权重动量
            private double mobp;// 动量系数
            private double rate;// 学习系数

            public BackPropagationDeep() {

            }

            public BackPropagationDeep(int[] layernum, double rate, double mobp) {
                this.mobp = mobp;
                this.rate = rate;
                // 初始化层数
                layer = new double[layernum.Length][];
                layerErr = new double[layernum.Length][];
                layer_weight = new double[layernum.Length][,];
                layer_weight_delta = new double[layernum.Length][,];
                Random random = new Random();
                // 初始化权重
                // l=layernum-1时只会设置layer和layerErr，而不会设置layer_weight和layer_weight_delta
                for (int l = 0; l < layernum.Length; l++) {
                    layer[l] = new double[layernum[l]];
                    layerErr[l] = new double[layernum[l]];
                    if (l + 1 < layernum.Length) {
                        // 前一层需要截距，在计算加权时要使用前一层截距项，但是产生的下一层结果项并不包含截距项
                        layer_weight[l] = new double[layernum[l] + 1, layernum[l + 1]];
                        layer_weight_delta[l] = new double[layernum[l] + 1, layernum[l + 1]];
                        for (int j = 0; j < layernum[l] + 1; j++)
                            for (int i = 0; i < layernum[l + 1]; i++)
                                layer_weight[l][j, i] = random.NextDouble();// 随机初始化权重
                    }
                }
            }

            // 逐层向前计算输出
            public double[] ComputeOut(double[] input) {
                // 从第二层开始，直到最后一层
                for (int l = 1; l < layer.Length; l++) {
                    for (int j = 0; j < layer[l].Length; j++) {
                        // z现在是截距的权重值
                        double z = layer_weight[l - 1][layer[l - 1].Length, j];
                        for (int i = 0; i < layer[l - 1].Length; i++) {
                            layer[l - 1][i] = l == 1 ? input[i] : layer[l - 1][i];
                            // 将各项加权累加到截距项z上面
                            z += layer_weight[l - 1][i, j] * layer[l - 1][i];
                        }
                        // 标准化
                        layer[l][j] = 1 / (1 + Math.Exp(-z));
                    }
                }
                // 返回最后一层结果
                return layer[layer.Length - 1];
            }

            // 逐层反向计算误差并修改权重
            public void UpdateWeight(double[] target) {
                int l = layer.Length - 1;
                // 对于最后一层，只调整Error，不需要调整weight
                for (int j = 0; j < layerErr[l].Length; j++)
                    layerErr[l][j] = layer[l][j] * (1 - layer[l][j]) * (target[j] - layer[l][j]);

                // --优先级高于>
                while (l-- > 0) {
                    // 前一层Error
                    for (int j = 0; j < layerErr[l].Length; j++) {
                        double z = 0.0;
                        // 后一层Error
                        for (int i = 0; i < layerErr[l + 1].Length; i++) {
                            // 计算前一层的特定值对后一层的每个值的Error的累计值，相当于最后一层的(target[j]-layer[l][j])
                            z = z + l > 0 ? layerErr[l + 1][i] * layer_weight[l][j, i] : 0;
                            // 隐含层动量调整，计算前一层的每个值对后一层的每个值的Error贡献多少，计算累加
                            layer_weight_delta[l][j, i] = mobp * layer_weight_delta[l][j, i]
                                    + rate * layerErr[l + 1][i] * layer[l][j];
                            // 隐含层权重调整
                            layer_weight[l][j, i] += layer_weight_delta[l][j, i];
                            // 如果j是前一层的最后一个值，则顺便将其截距项加以调整，计算其对后一层的值的Error贡献多少
                            if (j == layerErr[l].Length - 1) {
                                // 截距动量调整
                                layer_weight_delta[l][j + 1, i] = mobp * layer_weight_delta[l][j + 1, i]
                                        + rate * layerErr[l + 1][i];
                                // 截距权重调整
                                layer_weight[l][j + 1, i] += layer_weight_delta[l][j + 1, i];
                            }
                        }
                        // 记录误差
                        layerErr[l][j] = z * layer[l][j] * (1 - layer[l][j]);
                    }
                }
            }

            public void Train(double[] input, double[] tar) {
                // 正向计算得到输出
                ComputeOut(input);
                //将预测结果标准化
                for(int i = 0; i < tar.Length; i++)
                {
                    tar[i] = 1 / (1 + Math.Exp(0 - tar[i]));
                }
                // 反向调整权重和误差
                UpdateWeight(tar);
            }
        }
}